﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Cjc.ChromiumBrowser;
using TextBox = System.Windows.Controls.TextBox;
using System.Windows.Threading;
using _3DTools;
using System.Windows.Shapes;

namespace Cjc.Chromium3D
{
	/// <summary>
	/// Interaction logic for Window1.xaml
	/// </summary>
	public partial class Window1 : Window, INotifyPropertyChanged
	{
		private Trackball trackball = new Trackball();
		private BindingBase urlBinding;
		private Viewport2DVisual3D[] visuals;

		public Window1()
		{
			InitializeComponent();

			// Preserve url TextBox binding
			urlBinding = BindingOperations.GetBinding( url, TextBox.TextProperty );

			camera.Transform = trackball.Transform;
			light.Transform = trackball.Transform;

			MouseWheel += Window1_MouseWheel;
			viewport.MouseDown += viewport_MouseDown;
			Loaded += Window1_Loaded;

			camera.Changed += delegate { CheckVisibility(); };
		}

		void Window1_Loaded( object sender, RoutedEventArgs e )
		{
			Browsers = new[]
				{
					CreateBrowserState( "-0.5 0.5 0.5, 0.5 0.5 0.5, -0.5 -0.5 0.5, 0.5 -0.5 0.5", new Vector3D( 0, 0, 1 ), "http://chriscavanagh.wordpress.com" ),
					CreateBrowserState( "0.5 0.5 -0.5, -0.5 0.5 -0.5, 0.5 -0.5 -0.5, -0.5 -0.5 -0.5", new Vector3D( 0, 0, -1 ), "http://www.hulu.com" ),
					CreateBrowserState( "-0.5 0.5 -0.5, -0.5 0.5 0.5, -0.5 -0.5 -0.5, -0.5 -0.5 0.5", new Vector3D( -1, 0, 0 ), "http://www.silverlight.net" ),
					CreateBrowserState( "0.5 0.5 0.5, 0.5 0.5 -0.5, 0.5 -0.5 0.5, 0.5 -0.5 -0.5", new Vector3D( 1, 0, 0 ), "http://www.chriscavanagh.com/Chris/Silverlight/CJC.Silverlight.JelloPhysics/CJC.Silverlight.JelloPhysicsDemoTestPage.html" ),
					CreateBrowserState( "-0.5 0.5 -0.5, 0.5 0.5 -0.5, -0.5 0.5 0.5, 0.5 0.5 0.5", new Vector3D( 0, 1, 0 ), "http://www.bing.com" ),
					CreateBrowserState( "0.5 -0.5 -0.5, -0.5 -0.5 -0.5, 0.5 -0.5 0.5, -0.5 -0.5 0.5", new Vector3D( 0, -1, 0 ), "http://princeofcode.com/" )
				};

			RaisePropertyChanged( "Browsers" );
			RaisePropertyChanged( "BrowserVisuals" );

			SelectedBrowser = Browsers.First().Browser;
			RaisePropertyChanged( "SelectedBrowser" );

			visuals = BrowserVisuals.ToArray();

			Browsers.First().Browser.Focus();

			CheckVisibility();
		}

		public bool EnableAntiAliasing
		{
			get { return (EdgeMode)viewport.GetValue( RenderOptions.EdgeModeProperty ) != EdgeMode.Aliased; }
			set
			{
				viewport.SetValue( RenderOptions.EdgeModeProperty, value ? EdgeMode.Unspecified : EdgeMode.Aliased );
				//viewport.SetValue( RenderOptions.BitmapScalingModeProperty, value ? BitmapScalingMode.Fant : BitmapScalingMode.NearestNeighbor );
				//viewport.SetValue( RenderOptions.CachingHintProperty, value ? CachingHint.Cache : CachingHint.Unspecified );
			}
		}

		private double HomgeneousW( Point3D v, Matrix3D m )
		{
			return ( v.X * m.M14 )
				+ ( v.Y * m.M24 )
				+ ( v.Z * m.M34 )
				+ m.M44;
		}

		private Point3D TransformAndHomogenize( Point3D v, Matrix3D view, Matrix3D projection )
		{
			var vm = projection;
			vm.Prepend( view );

			v = vm.Transform( v );
			var w = HomgeneousW( v, projection );

			return new Point3D(
				v.X / w,
				v.Y / w,
				v.Z / w );
		}

		private void CheckVisibility()
		{
			if ( viewport.RenderSize.Height > 0 )
			{
				var view = MathUtils.GetViewMatrix( camera );
				var projection = MathUtils.GetProjectionMatrix( camera, viewport.RenderSize.Width / viewport.RenderSize.Height );
				var inverseCamera = camera.Transform.Value;
				inverseCamera.Invert();

				var transform = view;
				transform.Prepend( inverseCamera );

				// Make rear-facing browsers inactive
				foreach ( var v in visuals )
				{
					var geometry = (MeshGeometry3D)v.Geometry;
					var v1 = TransformAndHomogenize( geometry.Positions[ geometry.TriangleIndices[ 0 ] ], transform, projection );
					var v2 = TransformAndHomogenize( geometry.Positions[ geometry.TriangleIndices[ 1 ] ], transform, projection );
					var v3 = TransformAndHomogenize( geometry.Positions[ geometry.TriangleIndices[ 2 ] ], transform, projection );

					var size = viewport.RenderSize;
					var offset = new Point( size.Width / 2, size.Height / 2 );

					var visible = ( v1.Y * ( v3.X - v2.X ) + v2.Y * ( v1.X - v3.X ) + v3.Y * ( v2.X - v1.X ) ) > 0;

					var browser = (WebBrowser)v.Visual;
					if ( browser != null ) browser.IsActive = visible;

					var exists = viewport.Children.Contains( v );
					if ( visible && !exists ) viewport.Children.Add( v );
					else if ( !visible && exists ) viewport.Children.Remove( v );
				}
			}

			//debug.Text = "Visible: " + viewport.Children.Count.ToString();
		}

		private void Button_Click( object sender, RoutedEventArgs e )
		{
			Navigate();
		}

		void Window1_MouseWheel( object sender, MouseWheelEventArgs e )
		{
			trackball.Zoom( -e.Delta );
		}

		void viewport_MouseDown( object sender, MouseButtonEventArgs e )
		{
			var hit = VisualTreeHelper.HitTest( viewport, e.GetPosition( viewport ) );
			if ( hit == null ) return;

			var visual = hit.VisualHit;

			while ( visual != null && !( visual is Viewport2DVisual3D ) ) visual = VisualTreeHelper.GetParent( visual );

			if ( visual != null )
			{
				SelectedBrowser = (WebBrowser)( (Viewport2DVisual3D)visual ).Visual;
				RaisePropertyChanged( "SelectedBrowser" );

				if ( SelectedBrowser != null )
				{
					SelectedBrowser.Focus();
				}
			}
		}

		private BrowserState CreateBrowserState( string positions, Vector3D normal, string url )
		{
			var browser = new WebBrowser
			{
				Width = 1024,
				Height = 1024,
				Style = (Style)Resources[ "showFocus" ],
				Source = url,
				EnableAsyncRendering = false
			};

			return new BrowserState
			{
				Geometry = new MeshGeometry3D
				{
					Positions = Point3DCollection.Parse( positions ),
					TriangleIndices = Int32Collection.Parse( "2,1,0 2,3,1" ),
					TextureCoordinates = PointCollection.Parse( "0,0 1,0 0,1 1,1" ),
					Normals = new Vector3DCollection( new[] { normal, normal, normal, normal } )
				},
				Browser = browser
			};
		}

		public WebBrowser SelectedBrowser { get; set; }

		public ObservableCollection<Viewport2DVisual3D> BrowserVisuals
		{
			get
			{
				var aBitShiny = (Material)Resources[ "aBitShiny" ];

				return new ObservableCollection<Viewport2DVisual3D>( Browsers.Select( b => b.CreateVisual( aBitShiny ) ) );
			}
		}

		public IEnumerable<BrowserState> Browsers { get; set; }

		private void Navigate()
		{
			if ( SelectedBrowser != null )
			{
				var source = url.Text.Trim();

				// Replace TextBox binding (since OneWay, input will have cleared it)
				url.SetBinding( TextBox.TextProperty, urlBinding );

				if ( !source.Contains( ':' ) ) url.Text = source = "http://" + source;
				SelectedBrowser.Source = source;

				SelectedBrowser.Focus();
			}
		}

		private void ToggleRotate( object sender, RoutedEventArgs e )
		{
			var rotate = allowRotate.IsChecked.GetValueOrDefault( false );
			allowRotate.Content = rotate ? "Click to browse" : "Click to rotate or zoom";
			trackball.EventSource = rotate ? mainContent : null;
			viewport.IsHitTestVisible = !rotate;
			mainContent.Cursor = rotate ? Cursors.ScrollAll : Cursors.Arrow;

			SelectedBrowser.Focus();
		}

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		protected void RaisePropertyChanged( string propertyName )
		{
			if ( PropertyChanged != null ) PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
		}

		#endregion
	}
}
